package makemachine.demos.audio.microphone
{
	import com.bit101.components.*;
	
	import flash.display.*;
	import flash.events.*;
	import flash.media.*;
	import flash.net.FileReference;
	import flash.utils.ByteArray;
	
	import makemachine.demos.audio.microphone.capture.*;
	
	/**
	 * Document class. 
	 * Connects a MicrophoneInput an InputRenderer
	 * Generates and saves a .wav file of recorded audio
	 */
	[SWF( width="620", height="230", backgroundColor="0x222222" )]
	public class MicrophoneCapture extends Sprite
	{
		public static const BUFFER_SIZE:int = 8192;
		public static const SAMPLE_RATE:int = 44100;
		
		public function MicrophoneCapture()
		{
			addEventListener( Event.ENTER_FRAME, validateStage );
		}
		
		// --------------------------------------------------
		//
		// -- properties
		// 
		// --------------------------------------------------
		
		// -- mic & input
		protected var capture:MicrophoneInput;
		protected var renderer:InputRenderer;
		
		// -- playback
		protected var sound:Sound;
		protected var channel:SoundChannel;
		protected var playing:Boolean;
		protected var buffer:ByteArray;
		
		// -- view
		protected var gainMeter:Sprite;
		protected var rangeSlider:HRangeSlider
		protected var resolutionSlider:HUISlider;
		protected var recordPauseToggle:PushButton;
		protected var resetButton:PushButton;
		protected var playbackToggle:PushButton;
		protected var saveButton:PushButton;
		protected var gainSlider:HUISlider;
		
		// --------------------------------------------------
		//
		// -- protected event handlers
		// 
		// --------------------------------------------------
		
		/**
		 * Wait for the stage to be valid then initialize app
		 */
		protected function validateStage( event:Event ):void 
		{
			if( !stage ) return;
			if( !stage.stageWidth ) return;
			
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			
			createAudio();
			createDisplay();
			
			removeEventListener( Event.ENTER_FRAME, validateStage );
		}
		
		
		// -- If we are recording render the sample data
		protected function onMicSampleData( event:SampleDataEvent ):void
		{
			updateGainMeter();
			
			if( capture.recording )
			{
				renderer.render( event.data );
			}
		}
		
		// -- start / stop recording toggle
		protected function onRecordPauseToggle( event:Event ):void 
		{
			if( capture.recording )
			{
				stopRecording();
			} else {
				startRecording();
			}
		}
		
		// -- resets all parameters
		protected function onResetButton( event:Event ):void 
		{
			capture.reset();
			renderer.reset();
			rangeSlider.lowValue = rangeSlider.minimum;
			rangeSlider.highValue = rangeSlider.maximum;
			resolutionSlider.value = renderer.resolution 
			recordPauseToggle.label = 'Record';
			recordPauseToggle.selected = false;
		}
		
		// -- saves the current audio data as a .wav file
		protected function onSaveButton( event:Event ):void  
		{
			stopPlayback();
			stopRecording();
			
			var position:int = capture.buffer.position;
			var bytes:ByteArray = WavEncoder.encode( capture.buffer );
			var file:FileReference = new FileReference();
			
			file.save( bytes, 'sample.wav' );
			capture.buffer.position = position;
		}
		
		// -- update the range of data displayed in the renderer
		protected function onStartEndRangeSlider( event:Event ):void 
		{
			if( event.target is HRangeSlider )
			{
				var slider:HRangeSlider = event.target as HRangeSlider;
				renderer.endTime = slider.highValue * 1000;
				renderer.startTime = slider.lowValue * 1000;
				!capture.recording
					renderer.render();
			}
		}
		
		// -- update the resolution of the render, every 10th sample - every 500th sample
		protected function onResolutionSlider( event:Event ):void 
		{
			if( event.target is HUISlider )
			{
				var slider:HUISlider = event.target as HUISlider;
				renderer.resolution = slider.value;
				!capture.recording
					renderer.render();
			}
		}
		
		// -- attenuator for the microphone input
		protected function onGainSlider( event:Event ):void 
		{
			if( event.target is HUISlider )
			{
				var slider:HUISlider = event.target as HUISlider;
				capture.microphone.gain = slider.value;
			}
		}
		
		// -- start / stop playback toggle
		// -- fills a member buffer with the current recored audio
		protected function onPlaybackToggle( event:Event ):void 
		{
			if( playing )
			{
				stopPlayback();
			} else {
				resetBuffer();
				startPlayback();
			}
		}
		
		// -- writes recorded audio to the output stream
		protected function onPlaybackSampleData( event:SampleDataEvent ):void 
		{
			var i:int = 0;
			var n:Number = 0;
			while( i < BUFFER_SIZE )
			{
				if( buffer.bytesAvailable )
				{
					n = buffer.readFloat();
					event.data.writeFloat( n );
					i++;
					continue;
				}else {
					break;
				}
			}
		}	
		
		// -- stops audio
		protected function onPlaybackComplete( event:Event ):void 
		{
			stopPlayback();
		}
		
		// --------------------------------------------------
		//
		// -- protected methods
		// 
		// --------------------------------------------------
		
		protected function startRecording():void 
		{
			if( !capture.recording )
			{
				recordPauseToggle.label = 'Stop';
				recordPauseToggle.selected = true;
				stopPlayback();
				capture.record();
			}
			
		}
		
		protected function stopRecording():void 
		{
			if( capture.recording )
			{
				recordPauseToggle.label = 'Record';
				recordPauseToggle.selected = false;
				capture.stop();
			}
		}
		
		protected function startPlayback():void 
		{
			if( !playing )
			{
				playing = true;
				playbackToggle.label = 'Stop';
				playbackToggle.selected = true;
				stopRecording();
				sound.addEventListener( SampleDataEvent.SAMPLE_DATA, onPlaybackSampleData );
				channel = sound.play();
				channel.addEventListener( Event.SOUND_COMPLETE, onPlaybackComplete );
			}
		}
		
		protected function stopPlayback():void
		{
			if( playing )
			{
				playing = false;
				playbackToggle.label = 'Play';
				playbackToggle.selected = false;
				sound.removeEventListener( SampleDataEvent.SAMPLE_DATA, onPlaybackSampleData );
				if( channel ) 
				{
					channel.stop();
					channel.removeEventListener( Event.SOUND_COMPLETE, onPlaybackComplete );
					channel = null;
				}
			}
		}
		
		// -- copies the buffer from the MicrophoneInput class
		protected function resetBuffer():void 
		{
			var recordedBuffer:ByteArray = capture.buffer;
			var position:int = recordedBuffer.position;
			recordedBuffer.position = 0;
			buffer.clear();
			while( recordedBuffer.bytesAvailable )
				buffer.writeFloat( recordedBuffer.readFloat() );
			
			buffer.position = 0;
			recordedBuffer.position = position;
		}
		
		// --------------------------------------------------
		//
		// -- creation & drawing methods
		// 
		// --------------------------------------------------
		
		// -- set up audio vars
		protected function createAudio():void 
		{
			sound = new Sound();
			buffer = new ByteArray();
			
			capture = new MicrophoneInput();
			capture.init();
			capture.microphone.codec = SoundCodec.NELLYMOSER;
			capture.microphone.rate = 44;
			capture.microphone.setSilenceLevel( 0 );
			capture.addEventListener( SampleDataEvent.SAMPLE_DATA, onMicSampleData );
		}
		
		// -- build display
		protected function createDisplay():void 
		{
			renderer = new InputRenderer();	
			renderer.x = renderer.y = 5;
			
			gainMeter = new Sprite();
			gainMeter.x = 180;
			gainMeter.y = 205;
			
			rangeSlider = new HRangeSlider( this, 5, 155, onStartEndRangeSlider );
			rangeSlider.labelMode = 'never';
			rangeSlider.highValue = 30;
			rangeSlider.lowValue = 0;
			rangeSlider.minimum = 0;
			rangeSlider.maximum = 30;
			rangeSlider.width = 610;
			rangeSlider.height = 20;
			
			resolutionSlider = new HUISlider( this, 0, 0, 'Resolution', onResolutionSlider );
			resolutionSlider.setSliderParams( 10, 500, renderer.resolution );
			resolutionSlider.move( 5, 180 );
			resolutionSlider.tick = 1;
			resolutionSlider.labelPrecision = 0;
			resolutionSlider.width = 430;
			
			gainSlider = new HUISlider( this, 0, 0, 'Mic Gain', onGainSlider );
			gainSlider.move( 5, 205 );
			gainSlider.setSliderParams( 0, 100, 50 );
			gainSlider.labelPrecision = 0;
			gainSlider.value = capture.microphone.gain;
			
			recordPauseToggle = new PushButton( this, 410, 180, 'Record', onRecordPauseToggle );
			recordPauseToggle.toggle = true;
			
			resetButton = new PushButton( this, 410, 205, 'Reset', onResetButton );
			
			playbackToggle = new PushButton( this, 515, 180, "Play", onPlaybackToggle );
			playbackToggle.toggle = true;
			
			saveButton = new PushButton( this, 515, 205, 'Save', onSaveButton );
			
			renderer.render();
			updateGainMeter();
			
			addChild( renderer );
			addChild( gainMeter );
		}
		
		protected function updateGainMeter():void 
		{
			var g:Graphics = gainMeter.graphics;
			g.clear();
			g.beginFill( 0x353535, 1 );
			g.drawRect( 0, 0, 220, 15 );
			g.endFill();
			
			g.beginFill( 0x00C6FF, 1 );
			g.drawRect( 0, 0, ( capture.microphone.activityLevel / 100 * 220 ), 15 );
			g.endFill();
		}
	}
}